/*
 * Galaxium Messenger
 * Copyright (C) 2003-2007 Philippe Durand <draekz@gmail.com>
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.Collections;

using Gtk;
using Glade;
using GLib;

using Anculus.Core;
using Anculus.Gui;

using Galaxium.Client;
using Galaxium.Core;
using Galaxium.Gui;
using Galaxium.Gui.GtkGui;
using Galaxium.Protocol;
using Galaxium.Protocol.Gui;

namespace Galaxium.Client.GtkGui
{
	public sealed class ContainerWindow : IContainerWindow<Widget>
	{
		public event EventHandler WindowClosed;
		
		[Widget ("ContainerWindow")]
		private Window _window;
		[Widget ("panels")]
		private Notebook _notebook;
		[Widget ("menubar")]
		private MenuBar _menubar;
		[Widget ("toolbar")]
		private Toolbar _toolbar;
		
		private string _uid;
		private int _shake_count = 20;
		private int _shake_step = 0;
		private int _shake_x, _shake_y;
		private int[,] _shake_coords = new int [4,2] {{ -4, -4 }, { +4, +4 }, { +4, -4 }, { -4, +4 }};
		
		private System.Timers.Timer _shake_timer = new System.Timers.Timer (25);
		
		private IConfigurationSection _config = Configuration.Conversation.Section;
		private IList<IChatWidget<Widget>> _widgets;
		private IChatWidget<Widget> _currentWidget;
		private AccelGroup _accelGroup;
		private int _windowX = 0, _windowY = 0, _windowW = 0, _windowH = 0;
		
		public ContainerWindow (string uid)
		{
			if (uid == null)
				throw new ArgumentNullException ("uid");
			
			_uid = uid;
			_widgets = new List<IChatWidget<Widget>> ();
			
			lock (_shake_timer)
			{
				_shake_timer.AutoReset = true;
				_shake_timer.Elapsed += ShakeTimerElapsed;
			}
			
			_windowX = _config.GetInt ("CX", 0);
			_windowY = _config.GetInt ("CY", 0);
			_windowW = _config.GetInt ("CW", 0);
			_windowH = _config.GetInt ("CH", 0);
			
			// This is a tabbed window for all conversations.
			Initialize();
			
			_notebook.SwitchPage += new SwitchPageHandler(PageSwitchedEvent);
		}
		
		public bool Active
		{
			get { return _window.IsActive; }
		}
		
		public string UniqueIdentifier
		{
			get { return _uid; }
		}
		
		public Widget Toolbar
		{
			get { return _toolbar; }
		}
		
		public Widget Menubar
		{
			get { return _menubar; }
		}
		
		public IChatWidget<Widget> CurrentWidget
		{
			get { return _currentWidget; }
		}
		
		public IEnumerable<IChatWidget<Widget>> Widgets
		{
			get { return _widgets; }
		}
		
		public int ConversationCount
		{
			get { return _widgets.Count; }
		}
		
		public IEnumerable<IConversation> Conversations
		{
			get
			{
				foreach (IChatWidget<Widget> widget in _widgets)
					yield return widget.Conversation;
			}
		}
		
		private void SaveWindowLocation ()
		{
			int x, y, w, h;
			
			if (_window.GdkWindow.State == Gdk.WindowState.Maximized ||
				_window.GdkWindow.State == Gdk.WindowState.Iconified)
				return;
			
			_window.GetSize (out w, out h);
			_window.GetPosition (out x, out y);
			
			_config.SetInt ("CX", x);
			_config.SetInt ("CY", y);
			_config.SetInt ("CW", w);
			_config.SetInt ("CH", h);
		}
		
		public void AddConversation (IConversation conversation)
		{
			if (conversation == null)
				throw new ArgumentNullException ("conversation");
			
			IChatWidget<Widget> widget = ProtocolGuiUtility.CreateChatWidget<Widget> (conversation.Session.Account.Protocol, this, conversation);
			_widgets.Add (widget);
			
			try
			{
				widget.Initialize ();
			}
			catch (Exception ex)
			{
				Anculus.Core.Log.Error (ex, "Error initializing chat widget");
				return;
			}
			
			IContact contact = conversation.PrimaryContact;
			NotebookLabel label = null;
			
			if (contact != null)
				label = new NotebookLabel (widget, "", Markup.EscapeText(contact.DisplayIdentifier), "");
			else
				label = new NotebookLabel (widget, "", Markup.EscapeText(conversation.PrimaryContact.UniqueIdentifier), "");
			
			label.TabClosed += TabClosedEvent;
			
			_notebook.AppendPage (widget.NativeWidget, label);
			_notebook.SetTabLabelPacking (widget.NativeWidget, true, true, PackType.End);
			//_notebook.CurrentPage = _notebook.NPages - 1;
			_notebook.FocusInEvent += delegate {
				_currentWidget.Focus ();
			};
			
			ToggleTabVisibility ();
		}
		
		public void AddWidget (IChatWidget<Widget> widget)
		{
			_widgets.Add (widget);
			
			IContact contact = widget.Conversation.PrimaryContact;
			NotebookLabel label = null;
			
			if (contact != null)
				label = new NotebookLabel (widget, "", Markup.EscapeText(contact.DisplayIdentifier), "");
			else
				label = new NotebookLabel (widget, "", Markup.EscapeText(widget.Conversation.PrimaryContact.UniqueIdentifier), "");
			
			label.TabClosed += TabClosedEvent;
			
			_notebook.AppendPage (widget.NativeWidget, label);
			_notebook.SetTabLabelPacking (widget.NativeWidget, true, true, PackType.End);
			//_notebook.CurrentPage = _notebook.NPages - 1;
			
			ToggleTabVisibility ();
		}
		
		public void ShowConversation (IConversation conversation)
		{
			if (conversation == null)
				throw new ArgumentNullException ("conversation");
			
			int index = GetConversationIndex (conversation);
			if (index < 0)
				return;
			
			_notebook.CurrentPage = index;
		}
		
		public void RemoveConversation (IConversation conversation, bool closeWindow)
		{
			if (conversation == null)
				throw new ArgumentNullException ("conversation");
			
			int index = GetConversationIndex (conversation);
			if (index < 0)
				return;
			
			IChatWidget<Widget> widget = _widgets[index];
			
			_notebook.RemovePage (index);
			_widgets.RemoveAt (index);
			
			widget.Destroy();
			
			ToggleTabVisibility ();
			
			if (_widgets.Count == 0 && closeWindow)
				Close ();
		}
		
		public void RemoveConversations (ISession session, bool closeWindow)
		{
			if (session == null)
				throw new ArgumentNullException ("session");
			
			List<IConversation> tmp = new List<IConversation> ();
			foreach (IConversation conversation in Conversations)
				if (conversation.Session == session)
					tmp.Add (conversation);
			
			foreach (IConversation conversation in tmp)
				RemoveConversation (conversation, closeWindow);
			
			ToggleTabVisibility ();
		}
		
		public bool ContainsConversation (IConversation conversation)
		{
			if (conversation == null)
				throw new ArgumentNullException ("conversation");
			
			foreach (IChatWidget<Widget> widget in _widgets)
				if (widget.Conversation.UniqueIdentifier == conversation.UniqueIdentifier)
					return true;
			
			return false;
		}
		
		public void Shake ()
		{
			_window.GetPosition(out _shake_x, out _shake_y);
			
			_shake_count = 20;
			
			lock (_shake_timer)
			{
				if (!_shake_timer.Enabled)
					_shake_timer.Start ();
			}
		}
		
		private void ShakeTimerElapsed (object sender, System.Timers.ElapsedEventArgs args)
		{
			Random rnd = new Random();
			int change_x = _shake_coords[_shake_step, 0];
			int change_y = _shake_coords[_shake_step, 1];
			
			ThreadUtility.SyncDispatch (new VoidDelegate (delegate
			{
				_window.Move(_shake_x + change_x, _shake_y + change_y);
			}));
			
			_shake_step++;
			_shake_count--;
			
			if (_shake_step >= 3)
				_shake_step = 0;
			
			if (_shake_count < 1)
			{
				ThreadUtility.SyncDispatch (new VoidDelegate (delegate
				{
					_window.Move(_shake_x, _shake_y);
				}));
				
				lock (_shake_timer)
					_shake_timer.Stop();
				
				_shake_count = 20;
			}
		}
		
		public void Update ()
		{
			// Go through all the chat widgets and call update.
			foreach (IChatWidget<Widget> widget in _widgets)
			{
				widget.Update ();
			}
		}
		
		public void Show (bool minimized)
		{
			bool isVisible = _window.Visible;
			
			if (!_window.Visible && minimized)
				_window.Iconify();
			else
				isVisible = true;
			
			_window.Present ();
			
			// This is weird behavior, i'm not sure why it wont stay iconified when i select it the first time.
			if (!isVisible && minimized)
				_window.Iconify();
		}
		
		public void Close ()
		{
			List<IConversation> tmp = new List<IConversation> ();
			tmp.AddRange (Conversations);
			
			foreach (IConversation conversation in tmp)
			{
				conversation.Close ();
				RemoveConversation (conversation, false);
			}
			
			SaveWindowLocation ();
			
			if (WindowClosed != null)
				WindowClosed (this, new EventArgs());
		}
		
		public void Destroy ()
		{
			_window.Hide();
			_window.Destroy();
		}
		
		public void GenerateTitle ()
		{
			string title = "Error";
			IConversation conversation = _currentWidget.Conversation;
			
			if (conversation != null)
			{
				if (conversation.ContactCollection.Count > 1)
					title = "Group Chat";
				else
				{
					IContact contact = conversation.PrimaryContact;
					
					if (contact != null)
						title = MessageUtility.StripMarkup (contact.DisplayIdentifier, contact.Session.Account.Protocol);
				}
			}
			
			_window.Title = title;
		}
		
		public void GenerateAlert ()
		{
			if (!_window.HasFocus)
				_window.UrgencyHint = true;
		}
		
		private void Initialize ()
		{
			XML gxml = new XML (GladeUtility.GetGladeResourceStream (typeof (ContainerWindow).Assembly, "ContainerWindow.glade"), null, null);
			gxml.Autoconnect (this);
			
			_window.Icon = IconUtility.GetIcon ("galaxium-conversation", IconSizes.Large);
			_window.FocusInEvent += FocusInEvent;
			_window.FocusChildSet += FocusChildSet;
			
			_toolbar.ToolbarStyle = ToolbarStyle.BothHoriz;
			
			ToggleTabVisibility ();
			
			_window.Move (_windowX, _windowY);
			
			// Only resize if the values are non-zero
			// Otherwise, we'll stick with Gtks chosen size
			
			if ((_windowW * _windowH) > 0)
				_window.Resize (_windowW, _windowH);
		}
		
		private IChatWidget<Widget> GetConversationWidget (IConversation conversation)
		{
			foreach (IChatWidget<Widget> widget in _widgets)
				if (widget.Conversation == conversation)
					return widget;
			
			return null;
		}
		
		private int GetConversationIndex (IConversation conversation)
		{
			for (int i=0; i<_widgets.Count; i++)
				if (_widgets[i].Conversation == conversation)
					return i;
			
			return -1;
		}
		
		private void FocusChildSet (object sender, EventArgs args)
		{
			_window.UrgencyHint = false;
			
			NotebookLabel label = _notebook.GetTabLabel(_currentWidget.NativeWidget) as NotebookLabel;
			
			if (label != null)
				label.QuietActivity ();
			
			//_currentWidget.Focus();
		}
		
		private void FocusInEvent (object sender, EventArgs args)
		{
			if (_window != null)
			{
				_window.UrgencyHint = false;
				
				if(_notebook != null && _currentWidget != null)
				{
					NotebookLabel label = _notebook.GetTabLabel(_currentWidget.NativeWidget) as NotebookLabel;
					
					if (label != null)
						label.QuietActivity ();
					
					_currentWidget.Focus();
				}
			}
		}
		
		private void DeleteEvent (object sender, DeleteEventArgs args)
		{
			Close ();
			Destroy ();
		}
		
		private void TabClosedEvent (IConversation conversation)
		{
			RemoveConversation (conversation, true);
		}
		
		private void ToggleTabVisibility ()
		{
			if (_notebook.NPages < 2)
				_notebook.ShowTabs = false;
			else
				_notebook.ShowTabs = true;
		}
		
		private void PageSwitchedEvent (object sender, SwitchPageArgs args)
		{
			_currentWidget = _widgets[(int)args.PageNum];
			
			NotebookLabel label = _notebook.GetTabLabel(_currentWidget.NativeWidget) as NotebookLabel;
			if (label != null)
				label.QuietActivity ();
			
			GenerateTitle ();
			
			//regenerate the menu's
			WindowUtility<Widget>.LastConversationProtocol = _currentWidget.Conversation.Session.Account.Protocol;
			
			_currentWidget.SwitchTo ();
			
			// add the accel group after SwitchTo so that the menu has chance to be created
			if (_accelGroup != null)
				_window.RemoveAccelGroup (_accelGroup);
			
			_accelGroup = MenuUtility.GetAccelGroup (_window);
			_window.AddAccelGroup (_accelGroup);
			
			_currentWidget.Focus ();
		}
	}
}